home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / cache.c < prev    next >
Text File  |  1997-01-09  |  23KB  |  812 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     cache.c
  4.  
  5.     This module manages the article information cache.
  6.     
  7.     Copyright © 1994-1997, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14.  
  15. #include "glob.h"
  16. #include "cache.h"
  17. #include "dialog.h"
  18. #include "memutil.h"
  19. #include "text.h"
  20. #include "fileutil.h"
  21. #include "windutil.h"
  22. #include "resutil.h"
  23. #include "ic.h"
  24.  
  25.  
  26.  
  27. #define kCacheResourceType        'ACCH'
  28. #define kCacheGroupArrayID        128
  29. #define kCacheArticleArrayID    129
  30. #define kCacheStringsBlockID    130
  31.  
  32.  
  33.  
  34. typedef struct TGroupInfo {
  35.     long offset;                /* offset in strings block of group name */
  36.     long numCached;                /* number of cached articles in this group */
  37. } TGroupInfo;
  38.  
  39. typedef struct TArticleInfo {
  40.     long groupIndex;            /* index in group info array, or -1 if entry not used */
  41.     long number;                /* article number */
  42.     long subjectOffset;            /* offset in strings block of subject string */
  43.     long authorOffset;            /* offset in strings block of author string */
  44.     unsigned long creationDateTime;    /* date/time cache entry was created */
  45. } TArticleInfo;
  46.  
  47.  
  48. static Boolean gCacheDirty = false;            /* true if cache changed since read from prefs file */
  49. static TGroupInfo **gGroupInfo = nil;        /* handle to array of group info */
  50. static long gNumGroupInfo = 0;                /* number of elements in group info array */
  51. static TArticleInfo **gArticleInfo = nil;    /* handle to cached article info */
  52. static long gNumArticleInfo = 0;            /* number of elements in article info array */
  53. static Handle gStrings = nil;                /* handle to strings block */
  54. static long gStringsAllocated = 0;            /* number of bytes allocated in strings block */
  55. static long gStringsUsed = 0;                /* number of bytes used in strings block */
  56.     
  57.  
  58.  
  59. /*----------------------------------------------------------------------------
  60.     ValidCache 
  61.     
  62.     Validate the cache.
  63.             
  64.     Exit:    function result = true if no error.
  65. ----------------------------------------------------------------------------*/
  66.  
  67. static Boolean ValidCache (void)
  68. {
  69.     long i, j, numCached;
  70.     TArticleInfo *p;
  71.     TGroupInfo *q;
  72.     unsigned long nowDateTimePlus24Hours;
  73.  
  74.     GetDateTime(&nowDateTimePlus24Hours);
  75.     nowDateTimePlus24Hours += 24L*60L*60L;
  76.  
  77.     if (MyGetHandleSize(gGroupInfo) != gNumGroupInfo*sizeof(TGroupInfo)) goto exit;
  78.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  79.         if (q->offset < 0) goto exit;
  80.         if (q->offset >= gStringsUsed) goto exit;
  81.         if (strlen(*gStrings + q->offset) > 255) goto exit;
  82.         numCached = 0;
  83.         for (j = 0, p = *gArticleInfo; j < gNumArticleInfo; j++, p++)
  84.             if (p->groupIndex == i) numCached++;
  85.         if (numCached != q->numCached) goto exit;
  86.     }
  87.     
  88.     if (MyGetHandleSize(gArticleInfo) != gNumArticleInfo*sizeof(TArticleInfo)) goto exit;
  89.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  90.         if (p->groupIndex < 0) goto exit;
  91.         if (p->groupIndex >= gNumGroupInfo) goto exit;
  92.         if (p->number <= 0) goto exit;
  93.         if (p->subjectOffset < 0) goto exit;
  94.         if (p->subjectOffset >= gStringsUsed) goto exit;
  95.         if (strlen(*gStrings + p->subjectOffset) > 255) goto exit;
  96.         if (p->authorOffset < 0) goto exit;
  97.         if (p->authorOffset >= gStringsUsed) goto exit;
  98.         if (strlen(*gStrings + p->authorOffset) > 255) goto exit;
  99.         if (p->creationDateTime > nowDateTimePlus24Hours) goto exit;
  100.     }
  101.     
  102.     return true;
  103.     
  104. exit:
  105.  
  106.     ErrorMessageNumber(kStrDamagedCache);
  107.     return false;
  108. }
  109.     
  110.  
  111.  
  112. /*----------------------------------------------------------------------------
  113.     ReadArticleCache 
  114.     
  115.     Read the article cache from the prefs file.
  116.             
  117.     Exit:    function result = error code.
  118.     
  119.     This function must be called during initialization, when the prefs
  120.     file is read.
  121. ----------------------------------------------------------------------------*/
  122.  
  123. OSErr ReadArticleCache (void)
  124. {
  125.     OSErr err = noErr;
  126.  
  127.     err = MyGet1Resource(kCacheResourceType, kCacheGroupArrayID, &gGroupInfo);
  128.     if (err == resNotFound) {
  129.         err = MyNewHandle(0, &gStrings);
  130.         if (err != noErr) goto exit;
  131.         err = MyNewHandle(0, &gGroupInfo);
  132.         if (err != noErr) goto exit;
  133.         err = MyNewHandle(0, &gArticleInfo);
  134.         if (err != noErr) goto exit;
  135.     } else if (err == noErr) {
  136.         DetachResource((Handle)gGroupInfo);
  137.         gNumGroupInfo = MyGetHandleSize(gGroupInfo) / sizeof(TGroupInfo);
  138.         err = MyGet1Resource(kCacheResourceType, kCacheArticleArrayID, &gArticleInfo);
  139.         if (err != noErr) goto exit;
  140.         DetachResource((Handle)gArticleInfo);
  141.         gNumArticleInfo = MyGetHandleSize(gArticleInfo) / sizeof(TArticleInfo);
  142.         err = MyGet1Resource(kCacheResourceType, kCacheStringsBlockID, &gStrings);
  143.         if (err != noErr) goto exit;
  144.         DetachResource(gStrings);
  145.         gStringsAllocated = gStringsUsed = MyGetHandleSize(gStrings);
  146.         if (!ValidCache()) {
  147.             FlushArticleCache();
  148.             return userCanceledErr;
  149.         }
  150.     } else {
  151.         goto exit;
  152.     }
  153.     return noErr;
  154.     
  155. exit:
  156.  
  157.     MyDisposeHandle(gGroupInfo);
  158.     MyDisposeHandle(gArticleInfo);
  159.     MyDisposeHandle(gStrings);
  160.     gGroupInfo = nil;
  161.     gArticleInfo = nil;
  162.     gStrings = nil;
  163.     gNumGroupInfo = gNumArticleInfo = gStringsAllocated = gStringsUsed = 0;
  164.     gCacheDirty = true;
  165.     return err;
  166. }
  167.  
  168.  
  169.  
  170. /*----------------------------------------------------------------------------
  171.     CompactArticleCache 
  172.     
  173.     Compact the cache.
  174. ----------------------------------------------------------------------------*/
  175.  
  176. void CompactArticleCache (void)
  177. {
  178.     TGroupInfo **groupInfo = nil, *q1, *q2;
  179.     TArticleInfo **articleInfo = nil, *p1, *p2;
  180.     Handle strings = nil;
  181.     long numGroupInfo, numArticleInfo, stringsUsed, size, i, j, k;
  182.     OSErr err = noErr;
  183.     short sLen, aLen;
  184.     unsigned long nowDateTimeMinus60Days;
  185.  
  186.     if (gGroupInfo == nil) return;
  187.     GetDateTime(&nowDateTimeMinus60Days);
  188.     nowDateTimeMinus60Days -= 60L*24L*60L*60L;
  189.  
  190.     MySetHandleSize(gStrings, gStringsUsed);
  191.     
  192.     size = MyGetHandleSize(gGroupInfo);
  193.     err = MyNewHandle(size, &groupInfo);
  194.     if (err != noErr) goto exit;
  195.     numGroupInfo = 0;
  196.     
  197.     size = MyGetHandleSize(gArticleInfo);
  198.     err = MyNewHandle(size, &articleInfo);
  199.     if (err != noErr) goto exit;
  200.     BlockMoveData(*gArticleInfo, *articleInfo, size);
  201.     numArticleInfo = 0;
  202.     
  203.     size = MyGetHandleSize(gStrings);
  204.     err = MyNewHandle(size, &strings);
  205.     if (err != noErr) goto exit;
  206.     stringsUsed = 0;
  207.     
  208.     for (i = 0, j = 0, q1 = *groupInfo, q2 = *gGroupInfo; j < gNumGroupInfo; j++, q2++) {
  209.         if (q2->numCached > 0) {
  210.             q1->offset = stringsUsed;
  211.             q1->numCached = 0;
  212.             strcpy(*strings + stringsUsed, *gStrings + q2->offset);
  213.             stringsUsed += strlen(*strings + stringsUsed) + 1;
  214.             numGroupInfo++;
  215.             q1++;
  216.             if (i != j) {
  217.                 for (k = 0, p1 = *articleInfo; k < gNumArticleInfo; k++, p1++)
  218.                     if (p1->groupIndex == j) p1->groupIndex = i;
  219.             }
  220.             i++;
  221.         }
  222.     }
  223.     MySetHandleSize(groupInfo, numGroupInfo * sizeof(TGroupInfo));
  224.     
  225.     for (j = 0, p1 = *articleInfo, p2 = *articleInfo; j < gNumArticleInfo; j++, p2++) {
  226.         if (p2->groupIndex >= 0 && p2->groupIndex < numGroupInfo &&
  227.             p2->creationDateTime >= nowDateTimeMinus60Days) 
  228.         {
  229.             sLen = strlen(*gStrings + p2->subjectOffset);
  230.             aLen = strlen(*gStrings + p2->authorOffset);
  231.             p1->groupIndex = p2->groupIndex;
  232.             p1->number = p2->number;
  233.             strcpy(*strings + stringsUsed, *gStrings + p2->subjectOffset);
  234.             p1->subjectOffset = stringsUsed;
  235.             stringsUsed += sLen + 1;
  236.             strcpy(*strings + stringsUsed, *gStrings + p2->authorOffset);
  237.             p1->authorOffset = stringsUsed;
  238.             stringsUsed += aLen + 1;
  239.             p1->creationDateTime = p2->creationDateTime;
  240.             (*groupInfo)[p1->groupIndex].numCached++;
  241.             numArticleInfo++;
  242.             p1++;
  243.         }
  244.     }
  245.     MySetHandleSize(articleInfo, numArticleInfo * sizeof(TArticleInfo));
  246.     
  247.     MySetHandleSize(strings, stringsUsed);
  248.     
  249.     MyDisposeHandle(gGroupInfo);
  250.     gGroupInfo = groupInfo;
  251.     gNumGroupInfo = numGroupInfo;
  252.     MyDisposeHandle(gArticleInfo);
  253.     gArticleInfo = articleInfo;
  254.     gNumArticleInfo = numArticleInfo;
  255.     MyDisposeHandle(gStrings);
  256.     gStrings = strings;
  257.     gStringsUsed = gStringsAllocated = stringsUsed;
  258.     
  259.     gCacheDirty = true;
  260.     
  261.     return;
  262.     
  263. exit:
  264.  
  265.     MyDisposeHandle(strings);
  266.     MyDisposeHandle(groupInfo);
  267.     MyDisposeHandle(articleInfo);
  268. }
  269.  
  270.  
  271.  
  272. /*----------------------------------------------------------------------------
  273.     FlushArticleCache 
  274.     
  275.     Flush the cache.
  276. ----------------------------------------------------------------------------*/
  277.  
  278. void FlushArticleCache (void)
  279. {
  280.     if (gGroupInfo == nil) return;
  281.     MySetHandleSize(gGroupInfo, 0);
  282.     gNumGroupInfo = 0;
  283.     MySetHandleSize(gArticleInfo, 0);
  284.     gNumArticleInfo = 0;
  285.     MySetHandleSize(gStrings, 0);
  286.     gStringsUsed = gStringsAllocated = 0;
  287.     gCacheDirty = true;
  288. }
  289.  
  290.  
  291.  
  292. /*----------------------------------------------------------------------------
  293.     WriteArticleCache 
  294.     
  295.     Write the article cache to the prefs file.
  296.     
  297.     Entry:    newsServerAtStartup = news server name at startup.
  298.             
  299.     Exit:    function result = error code.
  300.     
  301.     This function must be called during termination, when the prefs file
  302.     is written.
  303. ----------------------------------------------------------------------------*/
  304.  
  305. OSErr WriteArticleCache (Str255 newsServerAtStartup)
  306. {
  307.     OSErr err = noErr;
  308.     
  309.     MyICReadSharedPrefs(kICNNTPHost);
  310.  
  311.     if (!gCacheDirty) return noErr;
  312.     
  313.     if (gGroupInfo == nil) {
  314.         err = MyNewHandle(0, &gGroupInfo);
  315.         if (err != noErr) return err;
  316.         err = MyNewHandle(0, &gArticleInfo);
  317.         if (err != noErr) return err;
  318.         err = MyNewHandle(0, &gStrings);
  319.         if (err != noErr) return err;
  320.     } else if (!EqualString(gPrefs.newsServerName, newsServerAtStartup, false, true)) {
  321.         FlushArticleCache();
  322.     } else {
  323.         CompactArticleCache();
  324.     }
  325.     
  326.     /* Rewrite the three cache resources on the prefs file. */
  327.     
  328.     err = MyReplaceResource(gGroupInfo, kCacheResourceType, kCacheGroupArrayID, "\p");
  329.     if (err != noErr) return err;
  330.     err = MyReplaceResource(gArticleInfo, kCacheResourceType, kCacheArticleArrayID, "\p");
  331.     if (err != noErr) return err;
  332.     err = MyReplaceResource(gStrings, kCacheResourceType, kCacheStringsBlockID, "\p");
  333.     if (err != noErr) return err;
  334.     return noErr;
  335. }
  336.  
  337.  
  338.  
  339. /*----------------------------------------------------------------------------
  340.     AddCachedArticle 
  341.     
  342.     Add an article to the cache.
  343.     
  344.     Entry:    groupName = group name.
  345.             number = article number.
  346.             subject = subject.
  347.             author = author.
  348.             
  349.     Exit:    function result = error code.
  350. ----------------------------------------------------------------------------*/
  351.  
  352. OSErr AddCachedArticle (char *groupName, long number, char *subject, char *author)
  353. {
  354.     long i;
  355.     TArticleInfo *p;
  356.     TGroupInfo *q;
  357.     long index = -1, groupIndex, subjectOffset, authorOffset, offset;
  358.     OSErr err = noErr;
  359.     short len, sLen, aLen;
  360.     
  361.     if (gGroupInfo == nil) return noErr;
  362.  
  363.     /* Check to see if this article is already in the cache.
  364.        Also get index = index in article info array of a free entry, or
  365.        -1 if none. */
  366.  
  367.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  368.         if (p->groupIndex >= 0) {
  369.             if (number == p->number &&
  370.                 strcmp(groupName, *gStrings + (*gGroupInfo)[p->groupIndex].offset) == 0) 
  371.                     return noErr;
  372.         } else {
  373.             index = i;
  374.         }
  375.     }
  376.     
  377.     /* If necessary, add the group name to the strings block and to the group info array. */
  378.     
  379.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  380.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  381.     }
  382.     if (groupIndex >= gNumGroupInfo) {
  383.         len = strlen(groupName);
  384.         if (gStringsUsed + len + 1 > gStringsAllocated) {
  385.             err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  386.             if (err != noErr) return err;
  387.             gStringsAllocated += 1000;
  388.         }
  389.         strcpy(*gStrings + gStringsUsed, groupName);
  390.         offset = gStringsUsed;
  391.         gStringsUsed += len+1;
  392.         err = MySetHandleSize(gGroupInfo, (gNumGroupInfo+1) * sizeof(TGroupInfo));
  393.         if (err != noErr) return err;
  394.         groupIndex = gNumGroupInfo;
  395.         q = &(*gGroupInfo)[groupIndex];
  396.         q->offset = offset;
  397.         q->numCached = 0;
  398.         gNumGroupInfo++;
  399.     }
  400.     
  401.     /* Add the subject and author strings to the strings block. */
  402.     
  403.     sLen = strlen(subject);
  404.     aLen = strlen(author);
  405.     if (gStringsUsed + sLen + aLen + 2 > gStringsAllocated) {
  406.         err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  407.         if (err != noErr) return err;
  408.         gStringsAllocated += 1000;
  409.     }
  410.     strcpy(*gStrings + gStringsUsed, subject);
  411.     subjectOffset = gStringsUsed;
  412.     gStringsUsed += sLen+1;
  413.     len = strlen(author);
  414.     strcpy(*gStrings + gStringsUsed, author);
  415.     authorOffset = gStringsUsed;
  416.     gStringsUsed += aLen+1;
  417.     
  418.     /* Add the new cache entry to the article info array. */
  419.     
  420.     if (index == -1) {
  421.         err = MySetHandleSize(gArticleInfo, (gNumArticleInfo+1) * sizeof(TArticleInfo));
  422.         if (err != noErr) return err;
  423.         index = gNumArticleInfo;
  424.         gNumArticleInfo++;
  425.     }
  426.     p = &(*gArticleInfo)[index];
  427.     p->groupIndex = groupIndex;
  428.     p->number = number;
  429.     p->subjectOffset = subjectOffset;
  430.     p->authorOffset = authorOffset;
  431.     GetDateTime(&p->creationDateTime);
  432.     
  433.     /* Increment the counter in the group info array. */
  434.     
  435.     (*gGroupInfo)[groupIndex].numCached++;
  436.     
  437.     gCacheDirty = true;
  438.     
  439.     return noErr;
  440. }
  441.  
  442.  
  443.  
  444. /*----------------------------------------------------------------------------
  445.     DeleteCachedArticle 
  446.     
  447.     Remove an article from the cache.
  448.     
  449.     Entry:    groupName = group name.
  450.             number = article number.
  451.             
  452.     Exit:    function result = error code.
  453. ----------------------------------------------------------------------------*/
  454.  
  455. OSErr DeleteCachedArticle (char *groupName, long number)
  456. {
  457.     long groupIndex, i;
  458.     TGroupInfo *q;
  459.     TArticleInfo *p;
  460.  
  461.     if (gGroupInfo == nil) return noErr;
  462.  
  463.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  464.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  465.     }
  466.     if (groupIndex >= gNumGroupInfo) return noErr;
  467.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  468.         if (p->groupIndex == groupIndex && p->number == number) {
  469.             p->groupIndex = -1;
  470.             (*gGroupInfo)[groupIndex].numCached--;
  471.             gCacheDirty = true;
  472.             return noErr;
  473.         }
  474.     }
  475.     return noErr;
  476. }
  477.  
  478.  
  479.  
  480. /*----------------------------------------------------------------------------
  481.     CompareArticleNumbers 
  482.     
  483.     Compare an article number to the article number in a TSubject record.
  484.     
  485.     Entry:    *number = article number.
  486.             x = pointer to TSubject record
  487.             
  488.     Exit:    function result = 
  489.                 -1 if article number < article number in TSubject record.
  490.                 0 if article number = article number in TSubject record.
  491.                 +1 if article number > article number in TSubject record.
  492. ----------------------------------------------------------------------------*/
  493.  
  494. static int CompareArticleNumbers (long *number, TSubject *x)
  495. {
  496.     if (*number < x->number) {
  497.         return -1;
  498.     } else if (*number == x->number) {
  499.         return 0;
  500.     } else {
  501.         return +1;
  502.     }
  503. }
  504.  
  505.  
  506.  
  507. /*----------------------------------------------------------------------------
  508.     AppendOneCachedArticle 
  509.     
  510.     Append one cached article for a group to the end of a subject array.
  511.     
  512.     Entry:    subjectArray = handle to subject array.
  513.             oldNumSubjects = number of elements in original subject array.
  514.             *numSubjects = current number of elements in subject array.
  515.             strings = handle to strings block for subject window.
  516.             number = article number.
  517.             subjectOffset = offset of subject string in strings block.
  518.             authorOffset = offset of author string in strings block.
  519.             
  520.     Exit:    function result = error code.
  521. ----------------------------------------------------------------------------*/
  522.  
  523. static OSErr AppendOneCachedArticle (TSubject **subjectArray, short oldNumSubjects,
  524.     long *numSubjects, Handle strings, long number, long subjectOffset, 
  525.     long authorOffset)
  526. {
  527.     TSubject *x;
  528.     OSErr err = noErr;
  529.     long sOffset, aOffset;
  530.     short sLen, aLen;
  531.     char state;
  532.     
  533.     /* Check to see if this article was already read from the net. */
  534.  
  535.     state = MyHGetState(subjectArray);
  536.     MyHLock(subjectArray);
  537.     x = bsearch(&number, *subjectArray, oldNumSubjects, sizeof(TSubject), 
  538.         (int(*)(const void *, const void *))CompareArticleNumbers);
  539.     MyHSetState(subjectArray, state);
  540.     if (x != nil) return noErr;
  541.     
  542.     /* Add the subject and author strings to the strings block for the 
  543.        subject window. */
  544.        
  545.     sLen = strlen(*gStrings + subjectOffset);
  546.     aLen = strlen(*gStrings + authorOffset);
  547.     sOffset = GetHandleSize(strings);
  548.     aOffset = sOffset + sLen + 1;
  549.     err = MySetHandleSize(strings, aOffset + aLen + 1);
  550.     if (err != noErr) return err;
  551.     strcpy(*strings + sOffset, *gStrings + subjectOffset);
  552.     strcpy(*strings + aOffset, *gStrings + authorOffset);
  553.        
  554.     /* Add a new TSubject element to the end of the subject array. */
  555.     
  556.     err = MySetHandleSize(subjectArray, (*numSubjects+1)*sizeof(TSubject));
  557.     if (err != noErr) return err;
  558.     x = &(*subjectArray)[*numSubjects];
  559.     x->number = number;
  560.     x->subjectOffset = sOffset;
  561.     x->authorOffset = aOffset;
  562.     x->read = true;
  563.     x->collapsed = gPrefs.showThreadsCollapsed;
  564.     x->inList = true;
  565.     (*numSubjects)++;
  566.     return noErr;
  567. }
  568.  
  569.  
  570.  
  571. /*----------------------------------------------------------------------------
  572.     AppendCachedArticles 
  573.     
  574.     Append all the cached articles for a group to the end of the subject
  575.     array for a subject window.
  576.     
  577.     Entry:    wind = pointer to subject window.
  578.             
  579.     Exit:    function result = error code.
  580. ----------------------------------------------------------------------------*/
  581.  
  582. OSErr AppendCachedArticles (WindowPtr wind)
  583. {
  584.     TWindow **info;
  585.     CStr255 groupName;
  586.     TSubject **subjectArray;
  587.     long numSubjects, oldNumSubjects;
  588.     long groupIndex, i;
  589.     TGroupInfo *q;
  590.     TArticleInfo *p;
  591.     Handle strings;
  592.     OSErr err = noErr;
  593.     
  594.     if (gGroupInfo == nil) return noErr;
  595.     
  596.     info = (TWindow**)GetWRefCon(wind);
  597.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  598.     subjectArray = (**info).subjectArray;
  599.     numSubjects = oldNumSubjects = (**info).numSubjects;
  600.     strings = (**info).strings;
  601.     
  602.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  603.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  604.     }
  605.     if (groupIndex >= gNumGroupInfo) return noErr;
  606.     
  607.     for (i = 0; i < gNumArticleInfo; i++) {
  608.         p = &(*gArticleInfo)[i];
  609.         if (p->groupIndex == groupIndex) {
  610.             err = AppendOneCachedArticle(subjectArray,  oldNumSubjects, 
  611.                 &numSubjects, strings, p->number, p->subjectOffset, 
  612.                 p->authorOffset);
  613.             if (err != noErr) return err;
  614.         }
  615.     }
  616.     
  617.     (**info).numSubjects = numSubjects;
  618.     
  619.     return noErr;
  620. }
  621.  
  622.  
  623.  
  624. /*----------------------------------------------------------------------------
  625.     AgeArticleCache 
  626.     
  627.     Age the cached articles for a group.
  628.     
  629.     Entry:    groupName = group name.
  630.             low = low article number for this group on the server.
  631.             
  632.     Exit:    function result = error code.
  633.                 
  634.     All cached articles for the group with article numbers less than
  635.     the low article number are deleted.
  636. ----------------------------------------------------------------------------*/
  637.  
  638. OSErr AgeArticleCache (char *groupName, long low)
  639. {
  640.     long groupIndex, i;
  641.     TGroupInfo *q;
  642.     TArticleInfo *p;
  643.     
  644.     if (gGroupInfo == nil) return noErr;
  645.  
  646.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  647.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  648.     }
  649.     if (groupIndex >= gNumGroupInfo) return noErr;
  650.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  651.         if (p->groupIndex == groupIndex && p->number < low) {
  652.             p->groupIndex = -1;
  653.             (*gGroupInfo)[groupIndex].numCached--;
  654.             gCacheDirty = true;
  655.         }
  656.     }
  657.     return noErr;
  658. }
  659.  
  660.  
  661.  
  662. /*----------------------------------------------------------------------------
  663.     DumpArticleCacheToFile 
  664.     
  665.     Dump the cache to a text file in human readable format (development
  666.     version only).
  667.     
  668.     Entry:    fSpec = pointer to file spec.
  669.             
  670.     Exit:    function result = error code.
  671. ----------------------------------------------------------------------------*/
  672.  
  673. #ifdef kDevelopmentVersion
  674.  
  675. OSErr DumpArticleCacheToFile (FSSpec *fSpec)
  676. {
  677.     OSErr err = noErr;
  678.     short refNum = 0;
  679.     CStr255 msg;
  680.     Str255 dateString, timeString;
  681.     long len, numCache = 0;
  682.     TArticleInfo *p;
  683.     TGroupInfo *q;
  684.     long i;
  685.     char state1, state2, state3;
  686.     
  687.     state1 = MyHGetState(gArticleInfo);
  688.     state2 = MyHGetState(gArticleInfo);
  689.     state3 = MyHGetState(gStrings);
  690.  
  691.     if (gGroupInfo == nil) {
  692.         ErrorMessage("There is no cache.");
  693.         return userCanceledErr;
  694.     }
  695.  
  696.     err = FSpOpenDF(fSpec, fsRdWrPerm, &refNum);
  697.     if (err != noErr) goto exit;
  698.  
  699.     MyHLock(gArticleInfo);
  700.     MyHLock(gGroupInfo);
  701.     MyHLock(gStrings);
  702.     
  703.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  704.         if (p->groupIndex >= 0) numCache++;
  705.     }
  706.     
  707.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  708.         sprintf(msg, "%9ld cached articles from %s\r", q->numCached, *gStrings + q->offset);
  709.         len = strlen(msg);
  710.         MyFSWriteNoCache(refNum, &len, msg, nil);
  711.     }
  712.     
  713.     sprintf(msg, "\r%9ld total articles in cache\r\r%9ld total bytes in cache\r\r",
  714.         numCache, MyGetHandleSize(gGroupInfo) + MyGetHandleSize(gArticleInfo) +
  715.         MyGetHandleSize(gStrings));
  716.     len = strlen(msg);
  717.     MyFSWriteNoCache(refNum, &len, msg, nil);
  718.     
  719.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  720.         if (p->groupIndex >= 0) {
  721.             sprintf(msg, "%ld\r   %s:%ld\r", i, 
  722.                 *gStrings + (*gGroupInfo)[p->groupIndex].offset,
  723.                 p->number);
  724.              len = strlen(msg);
  725.             MyFSWriteNoCache(refNum, &len, msg, nil);
  726.             sprintf(msg, "   %s\r", *gStrings + p->subjectOffset);
  727.             len = strlen(msg);
  728.             MyFSWriteNoCache(refNum, &len, msg, nil);
  729.             sprintf(msg, "   %s\r", *gStrings + p->authorOffset);
  730.             len = strlen(msg);
  731.             MyFSWriteNoCache(refNum, &len, msg, nil);
  732.             IUDateString(p->creationDateTime, shortDate, dateString);
  733.             IUTimeString(p->creationDateTime, false, timeString);
  734.             p2cstr(dateString);
  735.             p2cstr(timeString);
  736.             sprintf(msg, "   Created %s %s\r", dateString, timeString);
  737.             len = strlen(msg);
  738.             MyFSWriteNoCache(refNum, &len, msg, nil);
  739.         } else {
  740.             sprintf(msg, "%ld\r   *** unused ***\r", i);
  741.             len = strlen(msg);
  742.             MyFSWriteNoCache(refNum, &len, msg, nil);
  743.         }
  744.     }
  745.     
  746.     MyHSetState(gArticleInfo, state1);
  747.     MyHSetState(gGroupInfo, state2);
  748.     MyHSetState(gStrings, state3);
  749.     
  750.     MyFSClose(refNum, nil);
  751.     return noErr;
  752.     
  753. exit:
  754.     
  755.     if (refNum != 0) MyFSClose(refNum, nil);
  756.     MyHSetState(gArticleInfo, state1);
  757.     MyHSetState(gGroupInfo, state2);
  758.     MyHSetState(gStrings, state3);
  759.     return err;
  760. }
  761.  
  762. #endif 
  763.  
  764.  
  765.  
  766. /*----------------------------------------------------------------------------
  767.     DisplayArticleCache 
  768.     
  769.     Display the cache in a text window in human readable format (development
  770.     version only).
  771.     
  772.     Exit:    function result = error code.
  773. ----------------------------------------------------------------------------*/
  774.  
  775. #ifdef kDevelopmentVersion
  776.  
  777. OSErr DisplayArticleCache (void)
  778. {
  779.     FSSpec fSpec;
  780.     OSErr err = noErr;
  781.     Handle text = nil;
  782.     short refNum = 0;
  783.     long len;
  784.     WindowPtr wind;
  785.  
  786.     err = CreateTemporaryFile(&fSpec, kNewsWatcherSignature, 'ttxt', 'TEXT');
  787.     if (err != noErr) goto exit;
  788.     err = DumpArticleCacheToFile(&fSpec);
  789.     if (err != noErr) goto exit;
  790.     err = FSpOpenDF(&fSpec, fsRdPerm, &refNum);
  791.     if (err != noErr) goto exit;
  792.     err = MyNewHandle(kMaxShort, &text);
  793.     if (err != noErr) goto exit;
  794.     len = kMaxShort;
  795.     MyHLock(text);
  796.     err = FSRead(refNum, &len, *text);
  797.     MyHUnlock(text);
  798.     MySetHandleSize(text, len);
  799.     MyFSClose(refNum, nil);
  800.     err = MakeNewTextWindow("\pArticle Cache", 0, nil, text, &wind);
  801.     if (err != noErr) goto exit;
  802.     MyDisposeHandle(text);
  803.     return noErr;
  804.     
  805. exit:
  806.  
  807.     if (refNum != 0) MyFSClose(refNum, nil);
  808.     MyDisposeHandle(text);
  809.     return err;
  810. }
  811.  
  812. #endif